package com.tianlu.store.aspect;

import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson.JSON;
import com.tianlu.store.common.Constant;
import com.tianlu.store.common.NoCheckToken;
import com.tianlu.store.common.ResultCode;
import com.tianlu.store.common.ServiceException;
import com.tianlu.store.utils.JwtUtils;
import com.tianlu.store.utils.RedisTemplateUtils;
import com.tianlu.store.utils.ServletsUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;

/**
 * 校验签名。用户是否登录
 *
 * @author 谢庆成
 */
@Component
@Aspect
@Slf4j
@EnableConfigurationProperties
public class TokenAspect {
    @Pointcut("within(com.tianlu.store.controller.*))")
    private void controllerAspect() {

    }

    @Before("controllerAspect()")
    public void doBefore(JoinPoint joinPoint) {
        //获取请求信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            throw new ServiceException(ResultCode.CODE_500);
        }
        HttpServletRequest request = attributes.getRequest();
        String path = request.getRequestURI();
        String token = request.getHeader(Constant.HEADER_TOKEN);
        String timestamp = request.getHeader(Constant.HEADER_TIMESTAMP);
        String body = request.getHeader(Constant.POST_BODY);

        //从切入点上获取目标方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();

        // 签名字符串，未加签名
        Map<String, String> map = ServletsUtils.getParamMap(request);
        String urlParams = MapUtil.sortJoin(map, "&", "=", false, body);
        log.info("请求路径：{}", path);
        log.info("类名方法: {}.{}", methodSignature.getDeclaringTypeName(), methodSignature.getName());
        log.info("请求头参数：Token：{}，Timestamp：{}", token, timestamp);
        log.info("请求参数：{}", urlParams);
    }

    @Around("controllerAspect()")
    public Object execute(ProceedingJoinPoint pjp) throws Throwable {
        log.info("------------------------check start--------------------");
        long startTime = System.currentTimeMillis();

        //获取请求信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            throw new ServiceException(ResultCode.CODE_500);
        }
        HttpServletRequest request = attributes.getRequest();

        //从切入点上获取目标方法
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method method = methodSignature.getMethod();

        //默认需要check,若目标方法需要安全性检查,则进行Token验证，如果不进行此判断可以对所有HTTP请求进行安全性检查
        if (!method.isAnnotationPresent(NoCheckToken.class)) {
            checkToken(request);
        }

        // 调用目标方法
        Object result = pjp.proceed();

        log.info("返回结果: {}", JSON.toJSON(result));
        log.info("------------- 结束 耗时：{} ms -------------", System.currentTimeMillis() - startTime);

        return result;
    }

    /**
     * 验证token
     *
     * @param request 请求参数
     */
    private void checkToken(HttpServletRequest request) {
        // 获取当请求头中的token
        String authToken = request.getHeader(Constant.HEADER_TOKEN);

        // 验证token是否有效
        if (!JwtUtils.checkToken(authToken)) {
            log.info("token已失效！token:{}", authToken);
            throw new ServiceException(ResultCode.CODE_401);
        }

        // 验证token是否存在
        if (Objects.isNull(RedisTemplateUtils.get(Constant.USER_TOKEN_PREFIX + authToken))) {
            throw new ServiceException(ResultCode.USER_NOT_LOGIN);
        }

        //延长用户登录时长
        RedisTemplateUtils.expire(Constant.USER_TOKEN_PREFIX + authToken, Constant.USER_LOGIN_TOKEN_TIME);
    }
}